//
//  UINumberLabel.swift
//  iPhone
//
//  Created by Toj on 8/3/22.
//

import UIKit

private let UINumberRate: Float = 3

public enum UINumberLabelTransformStyle: Int {
    case easeInOut
    case easeIn
    case easeOut
    case linear
    case easeInBounce
    case easeOutBounce
    
    func update(t: Float) -> Float {
        switch self {
        case .easeInOut:
            return 1.0 - powf((1.0 - t), UINumberRate)
        case .easeIn:
            return powf(t, UINumberRate)
        case .easeOut:
            let nt = t * 2
            if nt < 1 {
                return 0.5 * powf(nt, UINumberRate)
            }
            
            return 0.5 * (2.0 - powf(2.0 - nt, UINumberRate))
        case .easeInBounce:
            if t < (4.0 / 11.0) {
                return 1.0 - (powf(11.0 / 4.0, 2) * powf(t, 2)) - t
            }
            
            if t < 8.0 / 11.0 {
                return 1.0 - (3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2)) - t
            }
            
            if t < 10.0 / 11.0 {
                return 1.0 - (15.0 / 16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2)) - t
            }
            
            return 1.0 - (63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2)) - t
        case .easeOutBounce:
            if t < 4.0 / 11.0 {
                return powf(11.0 / 4.0, 2) * powf(t, 2)
            }
            
            if t < 8.0 / 11.0 {
                return 3.0 / 4.0 + powf(11.0 / 4.0, 2) * powf(t - 6.0 / 11.0, 2)
            }
            
            if t < 10.0 / 11.0 {
                return 15.0 / 16.0 + powf(11.0 / 4.0, 2) * powf(t - 9.0 / 11.0, 2)
            }
            
            return 63.0 / 64.0 + powf(11.0 / 4.0, 2) * powf(t - 21.0 / 22.0, 2)
            
        default:
            return t
        }
    }
}

public typealias UINumberLabelFormatBlock = ((_ value: CGFloat) -> String)
public typealias UINumberLabelAttributedFormatBlock = ((_ value: CGFloat) -> NSAttributedString)

public typealias UINumberLabelCompletionBlock = (() -> Void)

open class UINumberLabel: UILabel {
    
    private var _format = "%f"
    public var format: String {
        set {
            _format = newValue
            setTextValue(currentValue)
        }
        
        get {
            return _format
        }
    }
    public var duration: TimeInterval = 2
    
    public var currentValue: CGFloat {
        get {
            if progress >= duration {
                return destinationValue
            }
            
            let percent = progress / duration
            let updateVal = transformStyle.update(t: Float(percent))
            return startingValue + (CGFloat(updateVal) * (destinationValue - startingValue))
        }
        
        set {
            setTextValue(newValue)
        }
    }
    
    private func setTextValue(_ value: CGFloat) {
        if attributedFormatBlock != nil {
            attributedText = attributedFormatBlock?(value)
            return
        }
        
        if formatBlock != nil {
            text = formatBlock?(value)
            return
        }
        
        guard let _ = format.range(of: "%[^fega]*[diouxc]", options: [.regularExpression, .caseInsensitive]) else {
            text = String(format: format, value)
            return
        }
        text = String(format: format, Int(value))
    }
    
    public var formatBlock: UINumberLabelFormatBlock?
    public var attributedFormatBlock: UINumberLabelAttributedFormatBlock?
    public var completionBlock: UINumberLabelCompletionBlock?
    
    private var startingValue = CGFloat.zero {
        didSet {
            currentValue = startingValue
        }
    }
    private var destinationValue = CGFloat.zero
    private var progress = TimeInterval.zero
    private var lastUpdate = TimeInterval.zero
    private var totalTime = TimeInterval.zero
    private var easingRate = CGFloat.zero
    
    private var timer: CADisplayLink?
    public var transformStyle: UINumberLabelTransformStyle = .linear
    
    public func toZero(duration: TimeInterval = 0) {
        to(0, duration: duration)
    }
    
    public func to(_ value: CGFloat, duration: TimeInterval = 0) {
        start(currentValue, end: value, duration: duration)
    }
    
    public func start(_ fromValue: CGFloat, end toValue: CGFloat, duration: TimeInterval = 2) {
        startingValue = fromValue
        destinationValue = toValue
        if duration <= 0 {
            setTextValue(toValue)
            completionBlock?()
            return
        }
        
        self.duration = duration
        
        easingRate = 3.0
        progress = 0
        lastUpdate = CACurrentMediaTime()
        startTimer()
    }
    
    @objc func updateValues(timer: CADisplayLink) {
        let now = CACurrentMediaTime()
        progress = progress + now - lastUpdate
        lastUpdate = now
        
        if progress >= duration {
            stopTimer()
            progress = duration
        }
        
        setTextValue(currentValue)
        
        if progress != duration { return }
        completionBlock?()
    }
    
    private func startTimer() {
        guard timer == nil else { return }
        
        let _timer = CADisplayLink(target: self, selector: #selector(updateValues(timer:)))
        // 1秒调用的次数, frame: 间隔帧数
        _timer.preferredFramesPerSecond = 30
        _timer.add(to: .main, forMode: .common)
        timer = _timer
    }
    
    private func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
}


